package user.servlet;

import static org.junit.Assert.*;

import java.net.HttpURLConnection;
import java.util.*;

import msg.*;

import org.apache.wink.client.*;
import org.json.*;
import org.junit.*;
import org.junit.Test;

import registration.*;
import user.*;

import common.dal.Transaction;
import common.servlet.ServletTests;

@SuppressWarnings({"javadoc", "static-method"})
public class UsersResourceTest extends ServletTests {
	
	private static final String	USER_NAME	= "gal";
	private static String		USERS;
	
	@BeforeClass
	public static void setupBeforeClass() throws Exception {
		ServletTests.setupBeforeClass();
		USERS = getResources() + "users";
	}
	
	@Test
	public void testRetrieve() throws Exception {
		// retrieve nothing
		Resource res = rc.resource(USERS + "/gal");
		ClientResponse response = res.get();
		assertEquals(HttpURLConnection.HTTP_NOT_FOUND, response.getStatusCode());
		postNewUser();
		// retrieve one user
		response = res.get();
		assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode());
		assertEquals(getDefaultJSON(), response.getEntity(JSONObject.class));
	}
	
	@Test
	public void testList() throws Exception {
		// retrieve empty list
		Resource res = rc.resource(USERS);
		ClientResponse response = res.get();
		JSONArray array = new JSONArray();
		assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode());
		assertEquals(array, response.getEntity(JSONArray.class));
		postNewUser();
		// retrieve list with one user
		array.put(getDefaultJSON());
		response = res.get();
		assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode());
		assertEquals(array, response.getEntity(JSONArray.class));
	}
	
	@Test
	public void testListPosts() throws Exception {
		// retrieve empty list
		Resource res = rc.resource(USERS + "/" + USER_NAME + "/posts");
		ClientResponse response = res.get();
		assertEquals(HttpURLConnection.HTTP_NOT_FOUND, response.getStatusCode());
		JSONArray array = new JSONArray();
		postNewUser();
		// retrieve list with no messages
		response = res.get();
		assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode());
		assertEquals(array, response.getEntity(JSONArray.class));
		// post one message
		t = new Transaction();
		User user = LoginManager.getNewInstance(t).get(USER_NAME);
		Message msg = MessageManager.getNewInstance(t)
				.createNewMessage(new Location(0.5, 0.5), "new content", user);
		t.commit();
		array.put(msg.accept(jsoner));
		response = res.get();
		assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode());
		assertEquals(array, response.getEntity(JSONArray.class));
		// post one event
		t = new Transaction();
		EventMessage event = MessageManager.getNewInstance(t)
				.createNewEventMessage(new Location(0.5, 0.5), "new content", user, getTomorrow(), 10);
		t.commit();
		array.put(event.accept(jsoner));
		response = res.get();
		assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode());
		assertEquals(array, response.getEntity(JSONArray.class));
	}
	
	@Test
	public void testListEvents() throws Exception {
		// retrieve empty list
		Resource res = rc.resource(USERS + "/" + USER_NAME + "/events");
		ClientResponse response = res.get();
		assertEquals(HttpURLConnection.HTTP_NOT_FOUND, response.getStatusCode());
		JSONArray array = new JSONArray();
		postNewUser();
		// retrieve list with no messages
		response = res.get();
		assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode());
		assertEquals(array, response.getEntity(JSONArray.class));
		// post one message
		t = new Transaction();
		User user = LoginManager.getNewInstance(t).get(USER_NAME);
		MessageManager.getNewInstance(t).createNewMessage(new Location(0.5, 0.5), "new content", user);
		t.commit();
		response = res.get();
		assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode());
		assertEquals(array, response.getEntity(JSONArray.class));
		// post one event
		t = new Transaction();
		EventMessage event = MessageManager.getNewInstance(t)
				.createNewEventMessage(new Location(0.5, 0.5), "new content", user, getTomorrow(), 10);
		t.commit();
		array.put(event.accept(jsoner));
		response = res.get();
		assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode());
		assertEquals(array, response.getEntity(JSONArray.class));
	}
	
	@Test
	public void testListMessages() throws Exception {
		// retrieve empty list
		Resource res = rc.resource(USERS + "/" + USER_NAME + "/messages");
		ClientResponse response = res.get();
		assertEquals(HttpURLConnection.HTTP_NOT_FOUND, response.getStatusCode());
		JSONArray array = new JSONArray();
		postNewUser();
		// retrieve list with no messages
		response = res.get();
		assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode());
		assertEquals(array, response.getEntity(JSONArray.class));
		// post one message
		t = new Transaction();
		User user = LoginManager.getNewInstance(t).get(USER_NAME);
		Message msg = MessageManager.getNewInstance(t)
				.createNewMessage(new Location(0.5, 0.5), "new content", user);
		t.commit();
		array.put(msg.accept(jsoner));
		response = res.get();
		assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode());
		assertEquals(array, response.getEntity(JSONArray.class));
		t = new Transaction();
		MessageManager.getNewInstance(t)
				.createNewEventMessage(new Location(0.5, 0.5), "new event content", user, getTomorrow(), 10);
		t.commit();
		response = res.get();
		assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode());
		assertEquals(array, response.getEntity(JSONArray.class));
	}
	
	@Test
	public void testMessagesSince() throws Exception {
		postNewUser();
		t = new Transaction();
		User user = LoginManager.getNewInstance(t).get(USER_NAME);
		Message msg = MessageManager.getNewInstance(t).createNewMessage(haifa, "msg", user);
		t.commit();
		Thread.sleep(10);
		Resource res = rc.resource(USERS + "/" + USER_NAME + "/messages?since=" + new Date().getTime());
		ClientResponse response = res.get();
		assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode());
		assertEquals(new JSONArray(), response.getEntity(JSONArray.class));
		res = rc.resource(USERS + "/" + USER_NAME + "/messages?since=" + msg.getTime());
		response = res.get();
		assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode());
		assertEquals(new JSONArray(Arrays.asList(msg.accept(jsoner))), response.getEntity(JSONArray.class));
	}
	
	@Test
	public void testEventsSince() throws Exception {
		postNewUser();
		t = new Transaction();
		User user = LoginManager.getNewInstance(t).get(USER_NAME);
		Calendar cal = Calendar.getInstance();
		cal.add(Calendar.MILLISECOND, 100);
		Message msg = MessageManager.getNewInstance(t).createNewEventMessage(haifa, "msg", user, cal, 10);
		t.commit();
		Thread.sleep(150);
		Resource res = rc.resource(USERS + "/" + USER_NAME + "/events?since=" + new Date().getTime());
		ClientResponse response = res.get();
		assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode());
		assertEquals(new JSONArray(), response.getEntity(JSONArray.class));
		res = rc.resource(USERS + "/" + USER_NAME + "/events?past=true&since=" + msg.getTime());
		response = res.get();
		assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode());
		assertEquals(new JSONArray(Arrays.asList(msg.accept(jsoner))), response.getEntity(JSONArray.class));
		res = rc.resource(USERS + "/" + USER_NAME + "/events?past=" + false);
		response = res.get();
		assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode());
		assertEquals(new JSONArray(), response.getEntity(JSONArray.class));
		res = rc.resource(USERS + "/" + USER_NAME + "/events");
		response = res.get();
		assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode());
		assertEquals(new JSONArray(), response.getEntity(JSONArray.class));
	}
	
	@Test
	public void testListRegisterations() throws Exception {
		// retrieve empty list
		Resource res = rc.resource(USERS + "/" + USER_NAME + "/registrations");
		ClientResponse response = res.get();
		assertEquals(HttpURLConnection.HTTP_NOT_FOUND, response.getStatusCode());
		JSONArray array = new JSONArray();
		postNewUser();
		// retrieve list with no messages
		response = res.get();
		assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode());
		assertEquals(array, response.getEntity(JSONArray.class));
		// register to a message
		t = new Transaction();
		User user = LoginManager.getNewInstance(t).get(USER_NAME);
		EventMessage event = MessageManager.getNewInstance(t)
				.createNewEventMessage(new Location(0.5, 0.5), "new content", user, getTomorrow(), 10);
		RegistrationManager.getNewInstance(t).registerUserForEvent(user, event);
		t.commit();
		array.put(event.accept(jsoner));
		response = res.get();
		assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode());
		assertEquals(array, response.getEntity(JSONArray.class));
	}
	
	@Test
	public void testListRegisterationsPast() throws Exception {
		// retrieve empty list
		Resource res = rc.resource(USERS + "/" + USER_NAME + "/registrations?past=false");
		ClientResponse response = res.get();
		assertEquals(HttpURLConnection.HTTP_NOT_FOUND, response.getStatusCode());
		JSONArray array = new JSONArray();
		postNewUser();
		// retrieve list with no messages
		response = res.get();
		assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode());
		assertEquals(array, response.getEntity(JSONArray.class));
		// register to a message
		t = new Transaction();
		Calendar cal = Calendar.getInstance();
		cal.add(Calendar.MILLISECOND, 100);
		User user = LoginManager.getNewInstance(t).get(USER_NAME);
		EventMessage event = MessageManager.getNewInstance(t)
				.createNewEventMessage(new Location(0.5, 0.5), "new content", user, cal, 10);
		RegistrationManager.getNewInstance(t).registerUserForEvent(user, event);
		t.commit();
		Thread.sleep(100);
		assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode());
		assertEquals(array, response.getEntity(JSONArray.class));
		array.put(event.accept(jsoner));
		res = rc.resource(USERS + "/" + USER_NAME + "/registrations?past=true");
		response = res.get();
		assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode());
		assertEquals(array, response.getEntity(JSONArray.class));
	}
	
	@Test
	public void testRegister() throws Exception {
		// // postNewUser tests a new user insertion
		JSONObject request = postNewUser();
		Resource res = rc.resource(USERS + "/gal");
		// second registration fails
		ClientResponse response = res.post(request);
		assertEquals(HttpURLConnection.HTTP_CONFLICT, response.getStatusCode());
	}
	
	@Test
	public void testLogin() throws Exception {
		// login unregistered user
		Resource res = rc.resource(USERS + "/login/gal");
		JSONObject request = new JSONObject("{password:'pass'}");
		ClientResponse response = res.post(request);
		assertEquals(HttpURLConnection.HTTP_FORBIDDEN, response.getStatusCode());
		postNewUser();
		// login registered user
		response = res.post(request);
		assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode());
		JSONObject jsonObject = new JSONObject("{id:0}");
		jsonObject.put("user", getDefaultJSON());
		assertTrue(response.getHeaders().getFirst("Set-Cookie").startsWith(UsersResource.COOKIE_NAME + "=0"));
		jsonObject.put("cookie-name", UsersResource.COOKIE_NAME);
		jsonObject.put("cookie", "0");
		assertEquals(jsonObject, response.getEntity(JSONObject.class));
		// login registered user again
		response = res.post(request);
		assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode());
		jsonObject = new JSONObject("{id:1}");
		jsonObject.put("user", getDefaultJSON());
		jsonObject.put("cookie-name", UsersResource.COOKIE_NAME);
		jsonObject.put("cookie", "1");
		assertTrue(response.getHeaders().getFirst("Set-Cookie").startsWith(UsersResource.COOKIE_NAME + "=1"));
		assertEquals(jsonObject, response.getEntity(JSONObject.class));
	}
	
	@Test
	public void testMe() throws Exception {
		// check with no cookie
		Resource res = rc.resource(USERS + "/me");
		ClientResponse response = res.get();
		assertEquals(HttpURLConnection.HTTP_UNAUTHORIZED, response.getStatusCode());
		// check with bad cookie
		response = res.cookie(createCookie("123")).get();
		assertEquals(HttpURLConnection.HTTP_UNAUTHORIZED, response.getStatusCode());
		// check with good cookie
		// login
		JSONObject user = postNewUser();
		user.remove("password");
		user.put("username", USER_NAME);
		res = rc.resource(USERS + "/login/gal");
		JSONObject request = new JSONObject("{password:'pass'}");
		response = res.post(request);
		String cookie = response.getEntity(JSONObject.class).getString("cookie");
		res = rc.resource(USERS + "/me").cookie(createCookie(cookie));
		response = res.get();
		assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode());
		assertEquals(user, response.getEntity(JSONObject.class));
	}
	
	@Test
	public void testLogout() throws Exception {
		Resource res = rc.resource(USERS + "/login/gal");
		ClientResponse response = res.delete();
		assertEquals(HttpURLConnection.HTTP_UNAUTHORIZED, response.getStatusCode());
		// logout no cookie
		postNewUser();
		res = rc.resource(USERS + "/login/gal");
		// logout bad cookie format
		response = res.header("Cookie", UsersResource.COOKIE_NAME + "=foobar").delete();
		assertEquals(HttpURLConnection.HTTP_BAD_REQUEST, response.getStatusCode());
		// logout unknown cookie
		response = res.header("Cookie", UsersResource.COOKIE_NAME + "=0").delete();
		assertEquals(HttpURLConnection.HTTP_NO_CONTENT, response.getStatusCode());
		// login user
		JSONObject request = new JSONObject("{password:'pass'}");
		res = rc.resource(USERS + "/login/gal");
		response = res.post(request);
		String cookie = response.getHeaders().getFirst("Set-Cookie").split("[=;]")[1];
		// log out mismatched user and cookie
		res = rc.resource(USERS + "/login/gal2");
		response = res.cookie(createCookie(cookie)).delete();
		assertEquals(HttpURLConnection.HTTP_FORBIDDEN, response.getStatusCode());
		// log out correct cookie
		res = rc.resource(USERS + "/login/gal");
		response = res.cookie(createCookie(cookie)).delete();
		assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode());
		// log out old cookie
		res = rc.resource(USERS + "/login/gal");
		response = res.cookie(createCookie(cookie)).delete();
		assertEquals(HttpURLConnection.HTTP_NO_CONTENT, response.getStatusCode());
	}
	
	@Test
	public void testUnregister() throws Exception {
		Resource res = rc.resource(USERS + "/" + USER_NAME + "/unregister");
		// delete non-existing user
		JSONObject request = new JSONObject("{password:'pass'}");
		ClientResponse response = res.post(request);
		assertEquals(HttpURLConnection.HTTP_FORBIDDEN, response.getStatusCode());
		postNewUser();
		// login user
		res = rc.resource(USERS + "/login/gal");
		response = res.post(request);
		String cookie = response.getHeaders().getFirst("Set-Cookie").split("[=;]")[1];
		// create message to makes sure it is for deleted after
		JSONObject newMessage = new JSONObject();
		newMessage.put("content", "new message");
		JSONObject location = new JSONObject();
		location.put("lat", 1.5);
		location.put("long", 1.5);
		newMessage.put("location", location);
		ClientResponse msgResponse = rc.resource(getResources() + "messages").cookie(createCookie(cookie))
				.post(newMessage);
		assertEquals(HttpURLConnection.HTTP_CREATED, msgResponse.getStatusCode());
		String messageLocation = msgResponse.getHeaders().getFirst("location");
		// create new event to make sure it is deleted after
		newMessage.put("time", getTomorrow().getTimeInMillis());
		newMessage.put("capacity", 10);
		msgResponse = rc.resource(getResources() + "events").cookie(createCookie(cookie)).post(newMessage);
		assertEquals(HttpURLConnection.HTTP_CREATED, msgResponse.getStatusCode());
		// create a new event from a different user, register to it, and vote on it
		String eventLocation = msgResponse.getHeaders().getFirst("location");
		t = new Transaction();
		LoginManager lm = LoginManager.getNewInstance(t);
		User user = lm.registerUser("a" + new Date().getTime(), "other user", PASSWORD);
		EventMessage event = MessageManager.getNewInstance(t)
				.createNewEventMessage(new Location(10.5, 32.5), "temp event", user, getTomorrow(), 10);
		RegistrationManager rm = RegistrationManager.getNewInstance(t);
		User testedUser = lm.get(USER_NAME);
		rm.registerUserForEvent(testedUser, event);
		Consensus con = rm.addConsensus(event, "con name");
		rm.vote(con, testedUser, false);
		t.commit();
		// unregister bad password
		res = rc.resource(USERS + "/" + USER_NAME + "/unregister");
		response = res.post(new JSONObject("{password: 'badpassword'}"));
		assertEquals(HttpURLConnection.HTTP_FORBIDDEN, response.getStatusCode());
		// unregister successfully
		response = res.post(request);
		assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode());
		
		// check that cookie has expired
		res = rc.resource(USERS + "/login/gal");
		response = res.cookie(createCookie(cookie)).delete();
		assertEquals(HttpURLConnection.HTTP_NO_CONTENT, response.getStatusCode());
		// make sure message was deleted
		response = rc.resource(messageLocation).get();
		assertEquals(HttpURLConnection.HTTP_NOT_FOUND, response.getStatusCode());
		// make sure event was deleted
		response = rc.resource(eventLocation).get();
		assertEquals(HttpURLConnection.HTTP_NOT_FOUND, response.getStatusCode());
		// check user no longer has no event registrations
		response = rc.resource(getResources() + "events/" + event.getId() + "/registered").get();
		assertEquals(new JSONArray(), response.getEntity(JSONArray.class));
		// check user no longer has any votes
		response = rc.resource(getResources() + "cons/" + con.getId() + "/votes").get();
		assertEquals(new JSONArray(), response.getEntity(JSONArray.class));
	}
	
	private JSONObject getDefaultJSON() {
		return new JSONObject("{username: 'gal', name: 'Gal Lalouche'}");
	}
	
	// returns the request used
	private JSONObject postNewUser() {
		Resource res = rc.resource(USERS + "/gal");
		JSONObject request = new JSONObject();
		request.put("name", "Gal Lalouche");
		request.put("password", "pass");
		ClientResponse response = res.post(request);
		assertEquals(HttpURLConnection.HTTP_CREATED, response.getStatusCode());
		assertEquals(getResources() + "users/gal", response.getHeaders().getFirst("location"));
		return request;
	}
}